package com.roy.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * @author ：楼兰
 * @date ：Created in 2021/6/3
 * @description:
 **/

public class NioServer {

    public static void main(String[] args) throws IOException {
        //1、绑定端口，开启服务
        final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        final Selector selector = Selector.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        //这里注意服务端的ServerSocketChannel也要注册到selector上。
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功");
        while (true){
            //2、阻塞，等到客户端事件发生。这里设置了超时时间。
            final int select = selector.select();
            if(select < 1){
                System.out.println("当前没有连接进来");
                continue;
            }
            //每个注册上来的channel都会对应一个selectionKey。
            final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                final SelectionKey key = iterator.next();
                //接收到的channel上发生的是一个accept事件
                if(key.isAcceptable()){
                    //这里serverSocketChannel.accept()会接收客户端的socketChannel连接请求，并返回对接好的socketChannel
                    //但是要注意，如果此时没有对应的客户端channel，他就会返回一个null。
                    final SocketChannel newSocketChannel = serverSocketChannel.accept();
                    System.out.println("收到客户端的连接请求:"+newSocketChannel.getRemoteAddress());
                    newSocketChannel.configureBlocking(false);
                    //将与客户端对接好的socketChannel重新注册到selector上，这次是关注READ数据读取时间。
                    //注册读事件时，需要绑定一个buffer相当于是附件。所有的数据交互都会写入到这个ByteBuffer中。
                    newSocketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(512));
                }
                //数据读取事件
                if(key.isReadable()){
                    //其他部分的代码基本都是模版，只有这一段是处理客户端请求的，需要定制一下。
                    handleKey(key);
                }
                // 把已经处理过的事件清除。防止重复处理。
                // 不然的话，对于连接的请求，服务端还是会去accept产生一个socketChannel。但是此时没有客户端来对接，就会返回一个Null。
                iterator.remove();
            }
        }
    }

    private static void handleKey(SelectionKey key) throws IOException {
        //通过selectionKey反查对应的channel。拿到channel后就可以用来跟客户端交互。
        final SocketChannel socketChannel = (SocketChannel)key.channel();
        //客户端的所有消息内容都会通过这个附件传递。注意，这个附件默认是不会清空的。
        ByteBuffer byteBuffer = (ByteBuffer)key.attachment();
        byteBuffer.clear();
        socketChannel.read(byteBuffer);
        System.out.println("收到客户端的消息："+new String(byteBuffer.array()));
        String response = "服务端收到你的消息："+new String(byteBuffer.array());
        socketChannel.write(ByteBuffer.wrap(response.getBytes()));
    }
}
